using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace Perovich.GameObjects.Helpers
{



    /// <summary>
    /// A singelton class that encapsulates collecting and responging to keyboard events.
    /// </summary>
    /// <remarks>Initialize must be called before using the Instance field.</remarks>
    public sealed class KeyboardHelper : Component
    {

        private static KeyboardHelper instance;

        /// <summary>
        /// Gets the currently in use instance of the <see cref="KeyboardHelper"/> class.
        /// </summary>
        public static KeyboardHelper Instance
        {
            get
            {
                if (instance == null)
                    throw new InvalidOperationException("Initialize must be called before accessing the Instance property.");
                return instance;
            }
            private set
            {
                instance = value;
            }
        }

        /// <summary>
        /// Initializes the <see cref="KeyboardHelper"/> using the specified <see cref="Perovich.GameObjects.Interfaces.ICanHoldComponents"/>.
        /// </summary>
        /// <param name="game">The <see cref="Perovich.GameObjects.Interfaces.ICanHoldComponents"/> instance to use.</param>
        public static void Initialize(Interfaces.ICanHoldComponents game)
        {
            if (instance == null)
            {
                GameObject obj;
                while ((obj = game as GameObject) != null)
                    game = obj.Parent;
                Instance = new KeyboardHelper(game);
                game.Components.Add(Instance);

                Perovich.GameObjects.ComponentsExtensions.Helpers.Add(new Helper(Instance.GetType().GetMethod("Reset", System.Reflection.BindingFlags.Static)));
            }
        }

        /// <summary>
        /// Resets the <see cref="KeyboardHelper"/>. For use after the <see cref="Perovich.GameObjects.Interfaces.ICanHoldComponents"/> is no longer in use.
        /// </summary>
        public static void Reset()
        {
            Instance = null;
        }




        private Dictionary<Keys, List<Action<Keys, KeyState>>> keyboardCallbacks;
        private Dictionary<Keys, List<Action<Keys, KeyState>>> keyboardPressCallbacks;
        private Dictionary<Keys, List<Action<Keys, KeyState>>> keyboardReleaseCallbacks;
        private Dictionary<Keys, KeyState?> lastKeyStates;

        private KeyboardHelper(Interfaces.ICanHoldComponents game)
            : base(game)
        {
            keyboardCallbacks = new Dictionary<Keys, List<Action<Keys, KeyState>>>();
            keyboardPressCallbacks = new Dictionary<Keys, List<Action<Keys, KeyState>>>();
            keyboardReleaseCallbacks = new Dictionary<Keys, List<Action<Keys, KeyState>>>();
            lastKeyStates = new Dictionary<Keys, KeyState?>();
        }

        /// <summary>
        /// Adds a callback function for the given key.
        /// </summary>
        /// <param name="key">The key to respond to.</param>
        /// <param name="callback">The callback to run.</param>
        public static void AddCallback(Keys key, Action<Keys, KeyState> callback)
        {
            Instance.AddCallbackInternal(key, callback);
        }
        private void AddCallbackInternal(Keys key, Action<Keys, KeyState> callback)
        {
            if (!keyboardCallbacks.ContainsKey(key))
            {
                keyboardCallbacks.Add(key, new List<Action<Keys, KeyState>>());
                if (!lastKeyStates.ContainsKey(key))
                    lastKeyStates.Add(key,null);
            }

            keyboardCallbacks[key].Add(callback);
        }

        /// <summary>
        /// Adds a key press callback function for the given key.
        /// </summary>
        /// <param name="key">The key to respond to.</param>
        /// <param name="callback">The callback to run.</param>
        public void AddPressCallback(Keys key, Action<Keys, KeyState> callback)
        {
            if (!keyboardPressCallbacks.ContainsKey(key))
            {
                keyboardPressCallbacks.Add(key, new List<Action<Keys, KeyState>>());
                if (!lastKeyStates.ContainsKey(key))
                    lastKeyStates.Add(key, null);
            }

            keyboardPressCallbacks[key].Add(callback);
        }

        /// <summary>
        /// Adds a key release callback function for the given key.
        /// </summary>
        /// <param name="key">The key to respond to.</param>
        /// <param name="callback">The callback to run.</param>
        public void AddReleaseCallback(Keys key, Action<Keys, KeyState> callback)
        {
            if (!keyboardReleaseCallbacks.ContainsKey(key))
            {
                keyboardReleaseCallbacks.Add(key, new List<Action<Keys, KeyState>>());
                if (!lastKeyStates.ContainsKey(key))
                    lastKeyStates.Add(key, null);
            }

            keyboardReleaseCallbacks[key].Add(callback);
        }

        /// <summary>
        /// Clears all key callbacks in the <see cref="KeyboardHelper"/>.
        /// </summary>
        public void Clear()
        {
            keyboardCallbacks.Clear();
            keyboardPressCallbacks.Clear();
            keyboardReleaseCallbacks.Clear();
            lastKeyStates.Clear();
        }

        /// <summary>
        /// Clears all key callbacks in the <see cref="KeyboardHelper"/> for the specified key.
        /// </summary>
        /// <param name="key">The key to clear.</param>
        public void ClearKey(Keys key)
        {
            keyboardCallbacks.Remove(key);
            keyboardPressCallbacks.Remove(key);
            keyboardReleaseCallbacks.Remove(key);
            lastKeyStates.Remove(key);
        }



        /// <summary>
        /// Updates the <see cref="KeyboardHelper"/>.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            foreach (Keys key in keyboardCallbacks.Keys)
            {
                if (Keyboard.GetState().IsKeyDown(key))
                {
                    foreach (Action<Keys, KeyState> callback in keyboardCallbacks[key])
                        callback(key,KeyState.Down);
                }
                else if (Keyboard.GetState().IsKeyUp(key))
                {
                    foreach (Action<Keys, KeyState> callback in keyboardCallbacks[key])
                        callback(key, KeyState.Up);
                }
            }
            foreach (Keys key in keyboardPressCallbacks.Keys)
            {
                if (Keyboard.GetState().IsKeyDown(key))
                {
                    if (lastKeyStates[key] == KeyState.Up)
                    {
                        foreach (Action<Keys, KeyState> callback in keyboardPressCallbacks[key])
                            callback(key, KeyState.Down);
                    }
                    lastKeyStates[key] = KeyState.Down;
                }
                else if (Keyboard.GetState().IsKeyUp(key))
                {
                    lastKeyStates[key] = KeyState.Up;
                }
            }

            foreach (Keys key in keyboardReleaseCallbacks.Keys)
            {
                if (Keyboard.GetState().IsKeyDown(key))
                {
                    lastKeyStates[key] = KeyState.Down;
                }
                else if (Keyboard.GetState().IsKeyUp(key))
                {
                    if (lastKeyStates[key] == KeyState.Down)
                    {
                        foreach (Action<Keys, KeyState> callback in keyboardReleaseCallbacks[key])
                            callback(key, KeyState.Up);
                    }
                    lastKeyStates[key] = KeyState.Up;
                }
            }

            base.Update(gameTime);
        }
    }
}
